﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using BrewTarget.Constants;

namespace BrewTarget.Utils
{

    public abstract class Algorithms
    {

        private static double round(double d)
        {
            return Math.Floor(d + 0.5);
        }



        private static double intPow(double inBase, int pow)
        {
            return Math.Pow(inBase, pow);
        }


        private static double polyEval(double[] poly, int order, double x)
        {
            double ret = 0.0;

            for (; order > 0; --order)
            {
                ret += poly[order] * intPow(x, order);
            }
            ret += poly[0];

            return ret;
        }


        private static double rootFind(double[] poly, int order, double x0, double x1)
        {
            double[] guesses = { x0, x1 };
            double newGuess = x0;
            double maxAllowableSeparation = Math.Abs(x0 - x1) * 1e3;

            while (Math.Abs(guesses[0] - guesses[1]) > AlgorithmConstants.ROOT_PRECISION)
            {
                newGuess = guesses[1] - (guesses[1] - guesses[0]) * polyEval(poly, order, guesses[1]) / (polyEval(poly, order, guesses[1]) - polyEval(poly, order, guesses[0]));

                guesses[0] = guesses[1];
                guesses[1] = newGuess;

                if (Math.Abs(guesses[0] - guesses[1]) > maxAllowableSeparation)
                    return double.MaxValue;
            }

            return newGuess;
        }


        private static double hydrometer15CCorrection(double celsius)
        {
            return polyEval(AlgorithmConstants.hydroCorrection15CPoly, AlgorithmConstants.hydroCorrection15CPoly_order, celsius) * (double)1e-3;
        }


        private static double SG_20C20C_toPlato(double sg)
        {
            return polyEval(AlgorithmConstants.PlatoFromSG_20C20C, AlgorithmConstants.PlatoFromSG_20C20C_order, sg);
        }


        private static double PlatoToSG_20C20C(double plato)
        {
            double[] poly = new double[AlgorithmConstants.PlatoFromSG_20C20C_order + 1];
            double ret;
            int i;

            // Copy the polynomial, cuz we need to alter it.
            for (i = 0; i <= AlgorithmConstants.PlatoFromSG_20C20C_order; ++i)
            {
                poly[i] = AlgorithmConstants.PlatoFromSG_20C20C[i];
            }

            // After this, finding the root of the polynomial will be finding the SG.
            poly[0] -= plato;

            ret = rootFind(poly, AlgorithmConstants.PlatoFromSG_20C20C_order, 1.000, 1.050);

            return ret;
        }

        private static double getPlato(double sugar_kg, double wort_l)
        {
            double water_kg = wort_l - sugar_kg / PhysicalConstants.sucroseDensity_kgL; // Assumes sucrose vol and water vol add to wort vol.

            return sugar_kg / (sugar_kg + water_kg) * 100.0;
        }




        private static double getWaterDensity_kgL(double celsius)
        {
            return polyEval(AlgorithmConstants.waterDensityPoly_C, AlgorithmConstants.waterDensityPoly_C_order, celsius);
        }

        private static double getABVBySGPlato(double sg, double plato)
        {
            // Implements the method found at:
            // http://www.byo.com/stories/projects-and-equipment/article/indices/29-equipment/1343-refractometers
            // ABV = [277.8851 - 277.4(SG) + 0.9956(Brix) + 0.00523(Brix^2) + 0.000013(Brix^3)] x (SG/0.79)


            // Deal with my unnecessary parentheses... DEAL WITH THEM!
            return (277.8851 - (277.4 * sg) + (0.9956 * plato) + (0.00523 * Math.Pow(plato, 2)) + (0.000013 * Math.Pow(plato, 3))) * (sg / 0.79);
        }

        private static double getABWBySGPlato(double sg, double plato)
        {
            // Implements the method found at:
            // http://primetab.com/formulas.html

            double ri = refractiveIndex(plato);
            return 1017.5596 - 277.4 * sg + ri * (937.8135 * ri - 1805.1228);
        }

        private static double sgByStartingPlato(double startingPlato, double currentPlato)
        {
            // Implements the method found at:
            // http://primetab.com/formulas.html

            double sp2 = startingPlato * startingPlato;
            double sp3 = sp2 * startingPlato;

            double cp2 = currentPlato * currentPlato;
            double cp3 = cp2 * currentPlato;

            return 1.001843 - 0.002318474 * startingPlato - 0.000007775 * sp2 - 0.000000034 * sp3
                   + 0.00574 * currentPlato + 0.00003344 * cp2 + 0.000000086 * cp3;

        }

        private static double ogFgToPlato(double og, double fg)
        {
            double sp = SG_20C20C_toPlato(og);

            double[] poly = {1.001843 - 0.002318474*sp - 0.000007775*sp*sp - 0.000000034*sp*sp*sp - fg,
                            0.00574,
                            0.00003344,
                            0.000000086};

            return rootFind(poly, 3, 3, 5);
        }

        private static double refractiveIndex(double plato)
        {
            // Implements the method found at:
            // http://primetab.com/formulas.html
            return 1.33302 + 0.001427193 * plato + 0.000005791157 * plato * plato;
        }

        private static double realExtract(double sg, double plato)
        {
            double ri = refractiveIndex(plato);
            return 194.5935 + 129.8 * sg + ri * (410.8815 * ri - 790.8732);
        }


    }
}
